Skip to content

feat: add strict latest signal linkage primitive#39

Closed
Pigbibi wants to merge 3 commits into
mainfrom
codex/p0-strict-linkage-primitive-v2
Closed

feat: add strict latest signal linkage primitive#39
Pigbibi wants to merge 3 commits into
mainfrom
codex/p0-strict-linkage-primitive-v2

Conversation

@Pigbibi

@Pigbibi Pigbibi commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add and export validate_latest_signal as a strict v2 linkage primitive
  • enforce one theme momentum snapshot source, bounded path resolution, same-file override, single-read hashing/parsing, optional hashes, and matching as_of
  • add focused regression tests without changing CLI/workflows

Validation

  • python3 -m pytest -q (51 passed)
  • git diff --check

No merge requested.

Co-Authored-By: Codex <noreply@openai.com>
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

🤖 Codex PR Review

🚫 Merge blocked: 1 serious issue(s) found in high-risk files

⚖️ Codex Review Arbitration

🚫 block: The current finding remains valid. In validate_latest_signal(), base is normalized with Path(signal_base_dir).resolve() and _resolve_source_path() accepts an absolute declared source if its resolved target is under that resolved base. But _read_declaration() ignores the already-resolved path argument and, when declared_source is absolute, recomputes containment with raw_path.relative_to(base). For a symlinked deploy path such as declared /deploy/current/theme_momentum_snapshot.json and resolved base /deploy/releases/2026-07-13, raw_path.relative_to(base) raises ValueError even though raw_path.resolve() equals the accepted in-base file. That incorrectly converts a valid declaration into absolute source must be beneath signal_base_dir. The prior timezone finding is independently addressed by _parse_datetime() now rejecting naive datetimes, so there is no contract conflict.

🚫 Blocking Issues

These issues must be fixed before this PR can be merged:

1. 🟠 [HIGH] Logic in src/research_signal_context_pipelines/latest_linkage.py

Absolute source paths under a symlinked signal_base_dir are rejected even when they resolve to the same file inside the base. validate_latest_signal() resolves signal_base_dir up front, so _resolve_source_path() correctly accepts something like /deploy/current/theme_momentum_snapshot.json when current -> releases/2026-07-13, but _read_declaration() then recomputes containment with raw_path.relative_to(base) against the unresolved absolute string. That raises ValueError and turns a valid in-base declaration into absolute source must be beneath signal_base_dir, breaking callers that use absolute paths in common symlinked deploy layouts. (line 107)

Suggestion: Derive the relative path from the already-resolved path argument (or from raw_path.resolve()) instead of from the raw absolute declaration string before walking with openat.

ℹ️ Other Findings

1. 🟡 [MEDIUM] Reliability in src/research_signal_context_pipelines/latest_linkage.py

The maximum artifact size check is raceable because it uses fstat() and then performs an unbounded stream.read(). If another process appends to the file after the size check but before/during the read, the validator will still read the enlarged file into memory, bypassing MAX_JSON_ARTIFACT_BYTES and potentially causing avoidable memory pressure or parsing/hashing bytes that should have been rejected. This is especially relevant because the code is explicitly trying to harden reads from shared artifact paths. (line 133)

Suggestion: Read a bounded amount from the descriptor, e.g. MAX_JSON_ARTIFACT_BYTES + 1 bytes (or metadata.st_size + 1 capped at that maximum), and fail closed if more data is available than the allowed limit.


Review by Codex PR Review bot • PR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cddb661244

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +65 to +66
if snapshot.get("as_of") != payload.get("as_of"):
raise SignalValidationError("signal and theme snapshot as_of must match")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject future-generated snapshots in strict linkage

When replaying or backfilling a saved signal, a theme snapshot with the same as_of but a generated_at later than the signal's generated_at still passes here because the linkage check only compares as_of. That admits a source artifact that did not exist when the signal was generated, which breaks the point-in-time guarantee and can introduce look-ahead into experiments; parse both timestamps and reject snapshots generated after the signal.

Useful? React with 👍 / 👎.

Comment on lines +115 to +116
if expected is None:
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require declared hashes to cover the linked source

When source_hashes is present but keyed with a different spelling of the same source path, this branch returns successfully instead of treating the missing digest for the sole linked source as a validation failure. That silently disables the integrity check even though the artifact declared hashes, so a tampered snapshot can pass if the hash key drifts; reject a supplied hash map that does not cover the declared/resolved source.

Useful? React with 👍 / 👎.

Comment on lines +63 to +64
if snapshot.get("mode") != "theme_momentum_snapshot":
raise SignalValidationError("source must declare theme_momentum_snapshot")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require the linked snapshot artifact type

When a source JSON keeps mode as theme_momentum_snapshot but sets another artifact_type, it passes here because the strict linkage only checks mode; the existing snapshot validator only checks that artifact_type is present. That lets a non-medium-horizon artifact be linked to a latest signal and pollute replay context; compare artifact_type to medium_horizon_theme_context before accepting the source.

Useful? React with 👍 / 👎.

Co-Authored-By: Codex <noreply@openai.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9703b54b17

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +179 to +180
try:
parsed = dt.datetime.fromisoformat(normalized)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject date-only generated_at values

When either the signal or snapshot supplies generated_at as a bare date such as 2026-06-27, datetime.fromisoformat() accepts it and this strict linkage check treats it as midnight UTC. That lets artifacts without an actual generation time pass the point-in-time ordering check, so same-day replay can be accepted even though the metadata cannot prove the snapshot existed when the signal was generated; require a time component before parsing.

Useful? React with 👍 / 👎.

Comment on lines +102 to +103
if base is None:
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Open declared absolute paths without following symlinks

When callers omit signal_base_dir for an absolute evidence.sources entry, path has already been produced by Path.resolve(), so a declared source that is a symlink is dereferenced before this O_NOFOLLOW open. In that absolute-path mode the validator accepts and hashes the symlink target instead of rejecting the declared source, which can link a latest signal to a swapped or future snapshot when hashes are omitted; keep the declared path for the no-follow open.

Useful? React with 👍 / 👎.

Co-Authored-By: Codex <noreply@openai.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12849f7df0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

_validate_source_hash(evidence, payload, declared_source, source_path, raw)
snapshot = _decode_declaration(raw)
try:
validate_theme_momentum_snapshot(snapshot)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject executable theme snapshot policies

When the linked v2 snapshot sets policy.execution_allowed or policy.portfolio_allocation_allowed to true, this still accepts it because validate_theme_momentum_snapshot() only checks that policy exists and the strict linkage adds no policy validation, even though the generated theme snapshot contract emits those flags as false for research-only context. That lets a latest signal validate against a source artifact that advertises execution/allocation permission, weakening the strict linkage guarantee for downstream consumers that trust validated artifacts.

Useful? React with 👍 / 👎.

Comment on lines +75 to +78
if _parse_datetime(snapshot["generated_at"], "theme snapshot generated_at") > _parse_datetime(
payload["generated_at"], "signal generated_at"
):
raise SignalValidationError("theme snapshot generated_at must not be later than signal generated_at")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject artifacts generated before their as_of date

When the snapshot claims as_of data from a future date relative to its own generated_at (for example as_of=2026-06-26 with snapshot generated_at=2026-06-25T00:00:00Z), this still passes as long as the signal uses the same as_of and was generated later. That permits a point-in-time replay source to claim data that could not have been available when the snapshot was generated, so strict linkage should compare the parsed as_of date against the generated timestamps, not only snapshot-vs-signal ordering.

Useful? React with 👍 / 👎.

Comment on lines +75 to +78
if _parse_datetime(snapshot["generated_at"], "theme snapshot generated_at") > _parse_datetime(
payload["generated_at"], "signal generated_at"
):
raise SignalValidationError("theme snapshot generated_at must not be later than signal generated_at")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject snapshots expired before signal generation

When a v2 snapshot has expires_at after as_of but before the signal's generated_at (for example a 2026-06-26 snapshot expiring 2026-06-28 linked to a signal generated 2026-07-27), this still validates because strict linkage only checks matching as_of and generation ordering. That allows a latest signal to be built from stale medium-horizon context even though the snapshot declares it was no longer valid at signal generation time.

Useful? React with 👍 / 👎.

@Pigbibi

Pigbibi commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Final user-authorized bounded remediation is exhausted and the required review remains blocking. Per the rollout contract, this PR is closed without merge; no further code changes or reslices will be attempted automatically.

@Pigbibi Pigbibi closed this Jul 13, 2026
@Pigbibi
Pigbibi deleted the codex/p0-strict-linkage-primitive-v2 branch July 13, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant